#include "Integer.hpp"
#include "Algorithm.hpp"
#include "GenericException.hpp"
#include "RSA.hpp"

RSA::RSA() {
}

const Integer& RSA::getN() const {
	return n;
}

const Integer& RSA::getE() const {
	return e;
}


const Integer& RSA::getD() const {
	return d;
}

Integer getRandomPrime(unsigned int bits) {
	Integer i;
	do {
	  i.randomize(bits);
	} while (!i.millerRabinMightBePrime(5));
	return i;
}

void RSA::setup(unsigned int bits) {

	Integer p;
	Integer q;
	
	do {
		p = getRandomPrime(bits);
		q = getRandomPrime(bits);
	} while (p == q);
	
	n = p * q;
	Integer phiN = (p - 1) * (q - 1);
	
	Integer gcd;
	do {
		e.randomize(bits);
		Integer::gcd(gcd, e, phiN);
	} while (e >= phiN || e == 1 || gcd != 1);
	
	Integer temp = 1;
	Integer temp2 = 1;
	
	Integer::extendedGcd(temp, temp2, d, phiN, e);
	if (d < 0) {
		d += phiN;
	}

	isSetup = true;
}

Block RSA::encrypt(const Block &plaintext) const {
	if (!isSetup) throw GENERICEXCEPTION("Cannot encrpyt when cryptosystem is not initialized.");
	Integer ciphertext(plaintext);

	return ciphertext.powMod(e, n).toBlock();
}

Block RSA::decrypt(const Block &ciphertext) const {
	if (!isSetup) throw GENERICEXCEPTION("Cannot decrpyt when cryptosystem is not initialized.");
	Integer plaintext(ciphertext);

	return plaintext.powMod(d, n).toBlock();
}

void RSA::dump() {
	std::cerr << "n := "; n.dump(); std::cerr << ";" << std::endl;
	std::cerr << "e := "; e.dump(); std::cerr << ";" << std::endl;
	std::cerr << "d := "; d.dump(); std::cerr << ";" << std::endl;
}

void RSA::writeParametersToFile(const std::string &Filename) const {
	if (!isSetup) throw GENERICEXCEPTION("Cannot write parameters when cryptosystem is not initialized.");
	FILE *f;
	f = fopen(Filename.c_str(), "w");
	if (f == NULL) return;
	n.writeToFile(f);
	e.writeToFile(f);
	d.writeToFile(f);
	fclose(f);
}

void RSA::readParametersFromFile(const std::string &Filename) {
	FILE *f;
	f = fopen(Filename.c_str(), "r");
	if (f == NULL) GENERICSTRMEXCEPTION("Cannot open " << Filename << " for reading.");
	n.readFromFile(f);
	e.readFromFile(f);
	d.readFromFile(f);
	fclose(f);
	isSetup = true;
}

unsigned int RSA::getBlockSize() const {
	return n.toBlock().getLength()-1;
	// take one byte less to be sure of correct length
}
